
昨天 kubectl apply 之後,如果沒看到 Running,最常見的就是 CrashLoopBackOff。
這個狀態的意思很直白:容器起來、掛掉、K8s 重啟它、又掛掉,於是 K8s 開始拉長重啟間隔(back off)。它不是一個錯誤,它是「有個東西一直讓容器退出」的訊號。今天講怎麼把訊號翻成原因。
一樣標 verified: false:這五種錯我在 compose 或 k3s 上都遇過類似的,但指令輸出的文字是依官方文件整理,沒有逐一重現截圖。
kubectl get pods
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous
get 看狀態和重啟次數(RESTARTS 那欄)。describe 看 K8s 的觀點:拉 image 有沒有成功、被 kill 的原因、Events 區塊的時間軸。
logs --previous 是關鍵。因為容器已經重啟了,logs 預設給你的是「現在這個」的輸出(可能是空的),--previous 才是「剛剛掛掉那個」臨終前說的話(容器沒起來過就沒有 previous,這時看 describe 的 Events)。
嚴格說這不是 CrashLoop,但它是第一個會撞到的。describe 的 Events 會寫 Failed to pull image。常見原因:image 名字打錯、tag 不存在、私有 registry 沒給認證。
AI 工程師特別容易撞後者,因為你的 bot image 多半放在自己的 GHCR 或 private registry。解法是建一個 registry 用的 Secret,在 Pod 的 imagePullSecrets 引用(步驟見官方 Pull an Image from a Private Registry),這是 Day 13 那份 YAML 刻意省略的。
容器起來、程式一開始讀 os.environ["BOT_TOKEN"]、KeyError、退出。logs --previous 會直接看到 Python traceback。
先分清楚兩種情況:Secret 名稱對不上(secretRef.name 打錯)或根本沒建,Pod 多半卡在 CreateContainerConfigError,容器根本沒起來;KeyError 則是 Secret 掛上了但 key 名不對。kubectl get secret 確認它存在,kubectl describe pod 看 Environment 區塊確認有掛上。
describe 裡 Last State: Terminated, Reason: OOMKilled。最常見是 limit 給太小;沒設 limit 時節點記憶體不夠也會發生,describe 的 Events 一起看。Day 09 講過這件事在 K8s 世界的意思:超過 limit 就是被殺,沒有商量。
我在 Day 13 給 bot 256Mi 的 limit,是基於它實測只吃數十 MiB。但如果你的 agent 會載入模型,或是 RAG 引擎那種(我的 devstack rag-engine 實測約 2.19 GiB),limit 要完全不同量級。先把 limit 調大觀察真實用量,再回頭收,比猜來得快。
bot 程式如果不是 long-running(例如少了 event loop,或 main 函式跑完就 return),容器會以 exit code 0 正常結束。然後 kubelet 依 restartPolicy: Always 在同一個 Pod 裡把容器再拉起來,於是又結束,重複幾次就進 CrashLoopBackOff。describe 會看到 Reason: Completed,exit code 0。
這在 compose 上不明顯(restart: unless-stopped 也會一直重啟,但你比較少盯著看),到 K8s 才被放大。修法在程式端,不在 YAML。
如果你有設 livenessProbe,而 bot 啟動比較慢(載入模型、連平台握手),probe 在它準備好之前就判定失敗,K8s 就殺掉重來,永遠起不來。describe 的 Events 會看到 Liveness probe failed。
修法是加 initialDelaySeconds,或改用 startupProbe。Day 13 的最小集刻意沒放 probe,就是為了先避開這一類。
我目前養成的順序是固定的:get(是什麼狀態、重啟幾次)→ describe(K8s 為什麼這樣對它)→ logs --previous(它自己怎麼說)。三步之內,大部分的 CrashLoop 都能定位。
剩下的,通常是網路(連不到平台或資料庫),那要 kubectl exec 進去用 curl 或 nc 試(一直重啟時 exec 不進去,得另開一個 debug Pod),超出今天範圍。
CrashLoopBackOff 不是錯誤,是「有個錯一直發生」;describe 看 K8s 的說法,logs --previous 看容器的遺言,兩邊對起來就找到了。